Repository navigation
fix(dash0): stop pre-filling a webmail domain as the first check target - #532
Merged
Merged
Conversation
The zero-check dashboard pre-fills the signup's email domain as the URL to monitor, skipping free webmail. The skip list only had .com names, so a @gmx.de, @yahoo.fr or @orange.fr signup was offered its mail provider as its first check. Country variants of the big providers (gmx.de, yahoo.co.uk, hotmail.fr, mail.ru) are now matched by brand, and national providers (web.de, t-online.de, orange.fr, free.fr, laposte.net...) are listed. The same rule backs the auto-join pattern check, so an admin can no longer claim @gmx\.de and auto-admit every signup from that provider. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #532 +/- ##
==========================================
+ Coverage 82.71% 82.76% +0.04%
==========================================
Files 1175 1175
Lines 121451 121460 +9
==========================================
+ Hits 100463 100525 +62
+ Misses 20981 20928 -53
Partials 7 7
🚀 New features to boost your workflow:
|
Merged
fclairamb
added a commit
that referenced
this pull request
Oct 14, 2026
🤖 I have created a release *beep* *boop* --- ## [0.38.4](v0.38.3...v0.38.4) (2026-10-14) ### Features * **http:** **HTTP results split the response time into DNS, connect, TLS, server and download time.** The result page shows a Timing card, and the check page has a Timing chart next to the response time. Each probe now opens a new connection, so a check that runs more often than every 90 s now counts connect and TLS time in its response time. The new metrics are `dns_time_ms`, `connect_time_ms`, `tls_time_ms`, `ttfb_time_ms`, `download_time_ms` and `redirect_time_ms`. ([9947d0b](9947d0b)) * **http:** **HTTP checks can test response headers with equals, contains, absent and other operators.** The new `headerAssertions` takes the same operators as body assertions, plus `exists` and `not_exists`. Rules like "`X-Powered-By` must not be sent" or "`Cache-Control` must not contain `no-store`" are now possible. The check form has a new "Header assertions" section, and `headers_pattern` keeps working. ([9947d0b](9947d0b)) * **http:** **An HTTP check can test where its redirects end up.** Set `expectedFinalUrl`, and the check goes down when the final URL is different, for example a login or parking page. The comparison ignores the case of the scheme and host, default ports and the fragment. The failure output includes `final_url`. ([9947d0b](9947d0b)) * **dash0:** **An HTTP check can be copied as a cURL command.** "Copy as cURL" is in the check page menu and on the incident page. The command has the method, headers, body, redirects, TLS verification, HTTP version and timeout. Secrets are never included: basic auth becomes `$SOLIDPING_BASIC_AUTH`, and a comment line says when secret headers were left out. ([9947d0b](9947d0b)) * **checks:** **Checks can send a new value on every run to get past a CDN cache.** `${var:timestamp}`, `${var:iso8601}`, `${var:uuid}` and `${var:random}` work anywhere `${env:...}` does, for example `?cb=${var:timestamp}`. Within one run, each name keeps the same value. The URL stored in a result now keeps `${param:...}` references instead of the secret values. ([9947d0b](9947d0b)) * **icmp:** **An ICMP check can go down on packet loss or a slow round trip.** Set `max_packet_loss_percent` or `max_avg_rtt_ms`. Before, one reply out of five kept the check up. The incident gives the reason, for example `packet loss 40% > 20%`. ([9947d0b](9947d0b)) * **notifications:** **Incident notifications say which regions see the failure.** Alerts include "Failing from: Europe West, US East (2 of 3 regions)", and resolved alerts include "Failed from: ...". The webhook payload has a new `incident.regions` with `failingRegions` (slugs), `regionCount` and `failQuorum`. It is recorded when the incident opens and updated when it escalates. ([9947d0b](9947d0b)) * **incidents:** **Open incidents that nobody has acknowledged can send reminders.** Set an interval from 15 min to 24 h on the check (`reminderIntervalSeconds`) or as an organization default. The check's channels then get `incident.reminder`: "Still down after 1 h 15 min, not acknowledged (reminder 3)". Reminders are off by default and stop on ack, snooze or resolve. Twilio and PagerDuty channels never get them. ([9947d0b](9947d0b)) * **mcp:** **The MCP server lists 23 tools by default instead of 46.** Tools are grouped in toolsets. `core` and `statuspages` are listed by default, and `admin` (integrations and maintenance windows) must be requested. Add `?toolsets=all` to the endpoint URL to list them all, or use `SP_MCP_TOOLSETS` or `--toolsets` with stdio. The 19 replaced tool names still work and are listed with `toolsets=legacy`. ([9947d0b](9947d0b)) * **helm:** **The Helm chart can run SolidPing on PostgreSQL through CloudNativePG.** Set `database.cnpg.enabled=true` to create a CNPG `Cluster` and read the connection string from its secret. The cluster survives `helm uninstall`. SQLite stays the default, so existing installs are not moved on upgrade. The Kubernetes install page now documents the chart. ([#544](#544)) ([30218d5](30218d5)) ### Bug Fixes * **discord:** **Discord buttons work when the bot has no Interactions Endpoint URL.** Acknowledge, I'm unavailable and Escalate failed with "The bot did not respond quick enough", and nothing showed in the logs. Discord sends the button press over the Gateway in that case, and SolidPing ignored it. It now answers it like the HTTPS endpoint does, so the endpoint URL is optional when the Gateway is on. ([#557](#557)) ([3b5937f](3b5937f)) * **http:** **An HTTP check with no expected status passes on any 2xx, not only 200.** Healthy endpoints answering 201, 202 or 204 showed as down. Existing checks change too. To keep the old behaviour, set `expectedStatusCodes: ["200"]`. ([9947d0b](9947d0b)) * **db:** **A first start that was interrupted no longer leaves a database that never starts again.** Stopping a container during its first start could end with `no such column: organization.default_escalation_policy_uid` on every boot. A failed or interrupted migration now stays pending and runs again. On SQLite, the first boot builds the database in a temporary file and renames it when done, and each upgrade first saves a backup (`solidping.db.pre-<migration>.bak`) when there is room on disk. ([#549](#549)) ([c7479ed](c7479ed)) * **webpush:** **Push notifications keep working after the first restart.** On a fresh install, the push key served during the first run was never stored, so the first restart made a new one and every browser subscribed in between stopped receiving pushes. The key is now stored before it is used. A browser hit by this can turn push off and on again. ([#539](#539)) ([caf64b4](caf64b4)) * **server:** **HEAD requests get the same answer as GET.** Every route answered `405 Method Not Allowed` to HEAD, so external uptime monitors that probe with HEAD saw SolidPing status pages as down. ([9947d0b](9947d0b)) * **checks:** **Run now during a run in progress now gives a result.** The request was dropped and the region reported `running`, so with a 1 h period the dashboard waited forever. The region now answers `queued` and runs again as soon as the current run ends. Editing a check while it runs no longer loses that run's result. ([9947d0b](9947d0b)) * **server:** **A fresh instance idles at about 30 MiB of its own memory instead of 140.** The seeded admin password was hashed with 64 MiB argon2id, and that memory stayed resident for minutes. Every login did the same. Measured on linux/arm64, RssAnon went from 141 to 31 MiB 60 s after first boot, and from 207 to 26 MiB 10 s after a login. The browser sample checks are now created disabled, and a Linux host with 2 GiB of RAM or less and no cgroup limit gets half its RAM as the soft memory cap (`SP_RUNTIME_AUTO_MEMORY_LIMIT=false` turns this off). ([9947d0b](9947d0b)) * **auth:** **On a host with 2 GiB of memory or less, password hashing uses the OWASP minimum argon2id profile.** A login now allocates 19 MiB instead of 64 MiB (`m=19456 t=2 p=1`). This applies only to single-process installs (`SP_NODE_ROLE=all`) where no argon2 cost is set. Existing hashes are converted at the next login. `SP_AUTH_PASSWORD_ARGON2_AUTO_PROFILE=false` keeps 64 MiB. ([9947d0b](9947d0b)) * **onboarding:** **A fresh install seeds 11 sample checks that work, instead of about 75.** Most samples targeted services nobody runs locally (databases, message brokers, game servers, test FTP servers), so about half of them were down and the first dashboard was red. The 11 seeded checks are HTTP, DNS, SSL and TCP checks against public services. Every sample is still in the create-check picker, and test and demo modes seed them all. ([#537](#537)) ([262c2cc](262c2cc)) * **logs:** **Logs pick a readable format on their own.** The new default format, `auto`, gives JSON on Kubernetes, colored output in a terminal or in a container, and plain text otherwise. `SP_LOG_FORMAT=json`, `text` or `pretty` still wins. The colored format has a timestamp and keeps attributes in a stable order. `NO_COLOR` and `FORCE_COLOR` are honored. `LOG_LEVEL` is no longer reset to info once the configuration loads. ([#536](#536)) ([4c13730](4c13730)) * **logs:** **A fresh server prints a few lines at startup and the URL to open.** It used to print 541 lines in its first minute and never said where to go. Check runs are DEBUG now. An INFO line is written when a check goes down, comes back up, warns or goes stale, and names the check. Workers and sample seeding print one line each. ([#538](#538)) ([bbe682a](bbe682a)) * **logs:** **A fresh start no longer warns about normal things.** The SQL errors on a missing table, the worker hostname, the retention setting, Sentry and Redis warnings are gone or moved to DEBUG. The metrics endpoint gets one line instead of two that contradicted each other. A fresh start now prints one WARN: credentials encryption is disabled. ([#539](#539)) ([caf64b4](caf64b4)) * **auth:** **Signing in on the demo org no longer files a join request.** A visitor who signed up from the live demo was told "A join request was sent to its admins", and nobody ever answered it. Sign-ins on the demo org, and on the `default` org in SaaS mode, now land on the create-your-org screen. On self-hosted installs, sign-ins on the `default` org still send join requests to its admins. ([9947d0b](9947d0b)) * **dash0:** **A first-time visitor is no longer told their session expired.** Opening `/` or `/d/` in a fresh browser showed "Your session has expired. Please log in again." The visitor now lands on the sign-in page with no banner. An expired or revoked session still shows it. ([9947d0b](9947d0b)) * **onboarding:** **The first check is no longer pre-filled with your mail provider.** The empty dashboard suggests your email domain as the first URL to monitor, and skips free webmail. The skip list only had `.com` names, so a `gmx.de`, `yahoo.fr` or `orange.fr` address got its mail provider suggested. Country variants and national providers are now skipped too. ([#532](#532)) ([b3e6672](b3e6672)) * **webhooks:** **Webhook signing secrets work as is with the official Standard Webhooks libraries.** New secrets are `whsec_` followed by standard base64 with padding. The dashboard and API show older secrets in the same form. It is the same key, so receivers that already verify keep working and nothing needs rotating. A pasted secret that is not valid base64 now gets a 400 on create and update. ([9947d0b](9947d0b)) * **config:** **`SP_AUDIT_RETENTION_DAYS=0` keeps audit events forever, as documented.** The value 0 was ignored, and the cleanup still deleted events older than 365 days. 0 now means "keep forever" in the env variable, in `config.yml` and in the `audit.retention_days` server parameter. ([9947d0b](9947d0b)) * **helm:** **Uploaded files survive a pod restart on PostgreSQL.** With `database.type=postgres`, `/data` was not mounted, so logos, status page assets and screenshots were lost on every restart. `/data` is now mounted whatever the database. ([#544](#544)) ([30218d5](30218d5)) * **dash0:** **Toasts no longer cover the Run now, Disable and Edit buttons on a check page.** After you created a check, the "Not on any status page yet" toast sat over these buttons. It is now a hint inside the check page, with its "Publish it" action. All toasts now start below the page header. ([9947d0b](9947d0b)) * **dash0:** **Region events name the region.** On the Events page, `region.offline` and `region.recovered` had an empty detail. They now show the region name. ([#552](#552)) ([f3ac93e](f3ac93e)) * **dash0:** **Page titles stay readable on a phone.** On narrow screens, the header buttons squeezed the title to one word per line, for example on SLOs and Escalation policies. The buttons now wrap below the title. ([#553](#553)) ([01d1c95](01d1c95)) * **dash0:** **The status page editor works on a phone.** Section headers and resource rows wrap instead of overflowing. ([#554](#554)) ([fb53d50](fb53d50)) * **http:** **Forcing HTTP/2 against an HTTP/1.1-only server always gives the clear error.** About 2% of such cleartext probes reported a generic connection error instead of "expected HTTP/2, but the server did not negotiate it". ([#547](#547)) ([b74bf7c](b74bf7c)) * **server:** **The platform watchdog no longer flags daily checks as stale.** A heartbeat check that runs once a day was reported as "silent for 95h" between the compaction of its last result and its next run. The watchdog now also reads the check's last result time. ([#542](#542)) ([bd4af36](bd4af36)) * **api:** **The number of check types is the same everywhere.** The docs said 39 in one place and 43 in another. Every page now says 43. `GET /api/v1/check-types` has a new `excludedFromTally` field, true for `sleep` and `private-location`. The incidents API docs also list the parameters the server actually reads (`state`, `checkUid`, `cursor`). ([9947d0b](9947d0b)) * **deps:** update github.com/dop251/goja digest to 0c308c5 ([#546](#546)) ([cba413a](cba413a)) * **deps:** update github.com/dop251/goja digest to 245c54f ([#551](#551)) ([90b9c25](90b9c25)) * **deps:** update github.com/dop251/goja digest to 480b366 ([#545](#545)) ([b86f196](b86f196)) * **deps:** update go dependencies (non-major) ([#543](#543)) ([87eb548](87eb548)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three defects on the signup path, found by reading PostHog replays of real signups that ended with zero checks.
1. Fixed: the first check target was the user's mail provider
The zero-check dashboard pre-fills the signup's email domain as the URL to monitor, and skips free webmail. The skip list only held
.comnames. A recent@gmx.designup gotgmx.depre-filled, never typed anything, and left without a check. Same for@yahoo.fr,@hotmail.co.uk,@web.de,@orange.frand so on.gmx.de,gmx.net,yahoo.co.uk,hotmail.fr,mail.ru. A brand label deeper in the name is not (mail.acme.comis still suggested).web.de,t-online.de,orange.fr,free.fr,laposte.net,libero.it,btinternet.com,uol.com.br...validateAutoJoinRegex, so an admin can no longer set.+@gmx\.deand auto-admit every signup from that provider. Nothing sets that pattern automatically, so this was defense in depth only.Files:
server/internal/handlers/auth/autojoin_regex.go(source of truth) andweb/dash0/src/lib/quick-target-suggestion.ts(mirror), with tests on both sides.2. Spec: a first-time visitor is told their session expired
specs/todos/2026-10-12-01-first-visit-no-session-expired-banner.md. Openingsolidping.ioin a fresh browser lands on/d/orgs/default/login?session_expired=truewith "Your session has expired". Cause:RootRedirectsends a visitor with no session to/no-org, its requests 401, andhandleResponsealways callsredirectToExpiredLogin(), which always sets the flag.3. Already filed: signing up from the demo files a join request to
demoCovered by
specs/todos/2026-10-10-02-demo-org-no-membership-request.md, nothing added here.Checks
go test -short ./internal/handlers/auth/passesgolangci-lint run ./...: 0 issuesvitest run src/lib/quick-target-suggestion.test.ts: 51 passed🤖 Generated with Claude Code